﻿#include "stdafx.h"
#if _QG_USE_GL
#include "qg_ogl_dev.h"
#include "qg_ogl_dev_ext.h"
#include "qg_ogl_mtrl.h"
#include "qg_ogl_draw.h"
#include "qg_ogl_cnv.h"
#include "qg_trace.h"

using namespace _ogl;

// 재질 바인드
bool GLDevice::_BindMtrl(ksize_t maxtexcount) throw()
{
	if (_sprn->bd_rasz)
	{
		_sprn->bd_rasz = false;
		_CommitRasterize(((GLRasz*)_sprn->m_rasz)->_data, _vprn.m_rasz_data);
	}

	if (_sprn->bd_blnd)
	{
		_sprn->bd_blnd = false;
		_CommitBlendState(((GLBlnd*)_sprn->m_blnd)->_data, _vprn.m_blnd_data);
	}

	for (ksize_t i = 0; i < maxtexcount; i++)
	{
		if (_sprn->bd_tex[i])
		{
			_sprn->bd_tex[i] = false;

			if (_CommitTexture((kint)i, _sprn->m_tex[i]) && _sprn->m_splr[i])
			{
				const qgSplrData& nsp = ((GLSplr*)_sprn->m_splr[i])->_data;
				qgSplrData& psp = ((GLTexture*)_sprn->m_tex[i])->_splr;

				if (memcmp(&nsp, &psp, sizeof(qgSplrData)-sizeof(tpl::Vec2)) != 0)
				{
					// SplrData는 24 바이트 고정이라 memcmp로 해도 괜찮을 것이다.
					// 근데 lodrange는 비교 안하니깐... 16바이트인가?
					_CommitSampler((kint)i, nsp, psp);
				}
			}
		}
		else if (_sprn->bd_splr[i] && _sprn->m_tex[i])
		{
			_sprn->bd_splr[i] = false;

			const qgSplrData& nsp = ((GLSplr*)_sprn->m_splr[i])->_data;
			qgSplrData& psp = ((GLTexture*)_sprn->m_tex[i])->_splr;

			if (memcmp(&nsp, &psp, sizeof(qgSplrData)-sizeof(tpl::Vec2)) != 0)
			{
				// SplrData는 24 바이트 고정이라 memcmp로 해도 괜찮을 것이다.
				// 근데 lodrange는 비교 안하니깐... 16바이트인가?
				_CommitSampler((kint)i, nsp, psp);
			}
		}
	}

	return true;
}

// 실제 털났나 비교
void GLDevice::_CommitRasterize(const qgRaszData& nrz, qgRaszData& prz) throw()
{
	// 깊이 바이어스, 내부에서 채우기 정보를 확인하므로 채우기 값을 변경하기 전에 처리해야한다
	if (nrz.dbias != prz.dbias || !k_eq(nrz.slope_scale, prz.slope_scale))
	{
		prz.dbias = nrz.dbias;
		prz.slope_scale = nrz.slope_scale;

		if (nrz.dbias != 0 || nrz.slope_scale != 0.0f)
		{
			glEnable(nrz.fill == QGFILL_POINT ? GL_POLYGON_OFFSET_POINT : nrz.fill == QGFILL_WIRE ? GL_POLYGON_OFFSET_LINE : GL_POLYGON_OFFSET_FILL);
			// 이 값이 범위가 맞는지는 나중에 확인하자.
			glPolygonOffset(nrz.slope_scale, (GLfloat)nrz.dbias);
		}
		else
		{
			// 이전 상태값을 보고 처리... 이대로 안전하겠지
			glDisable(prz.fill == QGFILL_POINT ? GL_POLYGON_OFFSET_POINT : prz.fill == QGFILL_WIRE ? GL_POLYGON_OFFSET_LINE : GL_POLYGON_OFFSET_FILL);
		}
	}

	// 채우기
	if (nrz.fill != prz.fill)
	{
		prz.fill = nrz.fill;

		glPolygonMode(GL_FRONT_AND_BACK, nrz.fill == QGFILL_POINT ? GL_POINT : nrz.fill == QGFILL_WIRE ? GL_LINE : GL_FILL);
	}

	// 컬링
	if (nrz.cull != prz.cull)
	{
		switch (nrz.cull)
		{
			case QGCULL_NONE:
				prz.cull = QGCULL_NONE;

				glDisable(GL_CULL_FACE);
				break;

			case QGCULL_FRONT:
				if (prz.cull == QGCULL_NONE)
					glEnable(GL_CULL_FACE);

				prz.cull = QGCULL_FRONT;

				glCullFace(GL_FRONT);
				break;

			case QGCULL_BACK:
				if (prz.cull == QGCULL_NONE)
					glEnable(GL_CULL_FACE);

				prz.cull = QGCULL_BACK;

				glCullFace(GL_BACK);
				break;
		}
	}

	// 깊이 쓰기
	if (nrz.dwrite != prz.dwrite)
	{
		prz.dwrite = nrz.dwrite;

		glDepthMask(nrz.dwrite ? GL_TRUE : GL_FALSE);
	}

	// 깊이 함수
	if (nrz.dfunc != prz.dfunc)
	{
		GLint n = GLConvert::ComparisonOp(nrz.dfunc);

		if (n == 0)
		{
			prz.dfunc = QGCOP_NONE;

			glDisable(GL_DEPTH_TEST);
		}
		else
		{
			if (prz.dfunc == QGCOP_NONE)
				glEnable(GL_DEPTH_TEST);

			prz.dfunc = nrz.dfunc;

			glDepthFunc(n);
		}
	}

	// 깊이 자르기(depth_clamp)
	// ARB, NV에만 있더라... 귀찮으니 패스
}

// 블렌디 한잔!!
void GLDevice::_CommitBlendState(const qgBlndData& nbd, qgBlndData& pbd) throw()
{
	// 블렌드 연산자
	GLenum blop = GLConvert::BlendOp(nbd.blend.op);

	// 색깔 쓰기 마스크
	if (nbd.mask != pbd.mask)
	{
		pbd.mask = nbd.mask;

		GLboolean r = nbd.mask & QGMASK_RED ? GL_TRUE : GL_FALSE;
		GLboolean g = nbd.mask & QGMASK_GREEN ? GL_TRUE : GL_FALSE;
		GLboolean b = nbd.mask & QGMASK_BLUE ? GL_TRUE : GL_FALSE;
		GLboolean a = nbd.mask & QGMASK_ALPHA ? GL_TRUE : GL_FALSE;
		glColorMask(r, g, b, a);
	}

	// 블렌드
	if (blop == 0)
	{
		// 블렌딩 끔
		if (_vprn.m_blnd_blend)
		{
			_vprn.m_blnd_blend = false;

			glDisable(GL_BLEND);
		}

		// 알파도 끄고, 다른 설정은 안해도 됨
		_vprn.m_blnd_alpha = false;

		pbd.blend.op = QGBLO_NONE;
		pbd.blend.src = QGBLV_NONE;
		pbd.blend.dest = QGBLV_NONE;
		pbd.alpha.op = QGBLO_NONE;
		pbd.alpha.src = QGBLV_NONE;
		pbd.alpha.dest = QGBLV_NONE;
	}
	else
	{
		GLenum alop = GLConvert::BlendOp(nbd.alpha.op);

		if (!_vprn.m_blnd_blend)
		{
			_vprn.m_blnd_blend = true;

			glEnable(GL_BLEND);
		}

		if (alop == 0)
		{
			// RGB만 사용
			if (nbd.blend.op != pbd.blend.op || nbd.blend.src != pbd.blend.src || nbd.blend.dest != pbd.blend.dest)
			{
				pbd.blend = nbd.blend;
				pbd.alpha.op = QGBLO_NONE;
				pbd.alpha.src = QGBLV_NONE;
				pbd.alpha.dest = QGBLV_NONE;

				QglBlendEquation(blop);

				QglBlendFuncSeparate(
					GLConvert::BlendValue(nbd.blend.src),
					GLConvert::BlendValue(nbd.blend.dest),
					GL_ONE, GL_ZERO);
			}

			// 알파 설정은 끔
			_vprn.m_blnd_alpha = false;
		}
		else
		{
			// RGB, ALPHA 둘다 사용
			if (nbd.blend.op != pbd.blend.op || nbd.blend.src != pbd.blend.src || nbd.blend.dest != pbd.blend.dest ||
				nbd.alpha.op != pbd.alpha.op || nbd.alpha.src != pbd.alpha.src || nbd.alpha.dest != pbd.alpha.dest)
			{
				pbd.blend = nbd.blend;
				pbd.alpha = nbd.alpha;

				QglBlendEquation(blop);

				QglBlendFuncSeparate(
					GLConvert::BlendValue(nbd.blend.src),
					GLConvert::BlendValue(nbd.blend.dest),
					GLConvert::BlendValue(nbd.alpha.src),
					GLConvert::BlendValue(nbd.alpha.dest));
			}

			// 알파 설정 켬
			_vprn.m_blnd_alpha = true;
		}
	}
}

// 샘플라
void GLDevice::_CommitSampler(kint stage, const qgSplrData& nsp, qgSplrData& psp) throw()
{
	// 텍스쳐를 먼저 설정해야하는 구나...
	if (!_ActiveTexture(stage))
		return;

	// 필터
	if (nsp.filt != psp.filt)
	{
		psp.filt = nsp.filt;

		GLint min, mag;
		/*bool b =*/ GLConvert::Filter(nsp.filt, _sprn->m_tex[stage]->HasMips(), &mag, &min);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min);

#if 0 && defined(GL_EXT_texture_filter_anisotropic)
		// 이것은 전역 필터 설정에 따르는 방식으로 하기로 함
		// 일단... 패스, 내비둠
		if (!b && _qs_ogl_test_caps(QSGLX_EXT_TEXTURE_FILTER_ANISOTROPIC))
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1);
#endif
	}

	// 가로 주소 처리 (U=S=가로)
	if (nsp.addru != psp.addru)
	{
		psp.addru = nsp.addru;

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GLConvert::Address(nsp.addru));
	}

	// 세로 주소 처리 (V=T=세로)
	if (nsp.addrv != psp.addrv)
	{
		psp.addrv = nsp.addrv;

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GLConvert::Address(nsp.addrv));
	}

	// 깊이 주소 처리 (W=R=깊이)
	if (nsp.addrw != psp.addrw)
	{
		psp.addrw = nsp.addrw;

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GLConvert::Address(nsp.addrw));
	}

	// cmpop 패스

	// 둘레
	if (nsp.border != psp.border)
	{
		psp.border = nsp.border;

		glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, tpl::Color(nsp.border).ToPointer());
	}

	// LOD 바이어스
	// 물론 사용자가 바꿀 수는 있지만 nsp.lod.max이 0보다 크면 GL_EXT_texture_lod_bias을 지원한다는 뜻으로 사용함
	if (nsp.lodrange.max > 0.0f && !k_eq(nsp.lodbias, psp.lodbias))
	{
		psp.lodbias = nsp.lodbias;

		float f = k_clamp(nsp.lodbias * 0.125f, nsp.lodrange.min, nsp.lodrange.max);
		glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, f);
	}
}

//
qgRasz* GLDevice::_RoamImplRasz(const qgRaszData& data)
{
	GLRasz* rasz = kobj_create(GLRasz);

	rasz->_data = data;

	return rasz;
}

//
qgBlnd* GLDevice::_RoamImplBlnd(const qgBlndData& data)
{
	GLBlnd* blnd = kobj_create(GLBlnd);

	blnd->_data = data;

	return blnd;
}

//
qgSplr* GLDevice::_RoamImplSplr(const qgSplrData& data)
{
	GLSplr* splr = kobj_create(GLSplr);

	splr->_data = data;

	return splr;
}


//////////////////////////////////////////////////////////////////////////
// 래스터라이저
KIMPL_SUBCLASS(GLRasz, "GLRasz");


//////////////////////////////////////////////////////////////////////////
// 블랜드
KIMPL_SUBCLASS(GLBlnd, "GLBlnd");


//////////////////////////////////////////////////////////////////////////
// 샘플러
KIMPL_SUBCLASS(GLSplr, "GLSplr");

#endif	// _QG_USE_GL
